package com.gitee.wsl.io.binary

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty


/**
 * The binary data type for a struct.
 *
 * This type does not use the [parse] and [store] methods, instead using delegates to modify the data in-place.
 * Any data structures defined as structs should be implemented as subclasses of [StructData].
 *
 * Example usage:
 *
 * ```
 * class RarBlock(data: ByteArray, offset: Int) : StructData(data, offset) {
 *     // In this example we only read them, so they're defined as `val`.
 *     // If you need to write them, use `var` instead.
 *     val crc by ushort
 *     val type by ubyte
 *     val flags by ushort
 *     val size by ushort
 *     val addedSize by uint
 * }
 *
 * fun getRarBlockSize(data: ByteArray): Int {
 *     val block = RarBlock(data, 0)
 *     return block.size + block.addedSize
 * }
 * ```
 *
 * @param data The backing data for the struct.
 * @param offset The offset into the data to start reading/writing from.
 */
open class StructData<K:StructData<K>>() : BinaryData<K> {

    internal val fields:MutableList<Prop<*>> = ArrayList()

    internal var currOffset: Int = 0

    protected fun byte( default:Byte  = Byte.MIN_VALUE)  = Prop(ByteData,default)
    protected fun uByte(default:UByte  = UByte.MIN_VALUE) = Prop( UByteData,default)
    protected fun short(default:Short  = Short.MIN_VALUE) = Prop( ShortData,default)
    protected fun uShort(default: UShort  = UShort.MIN_VALUE) = Prop( UShortData,default)
    protected fun int(default:Int  = Int.MIN_VALUE) = Prop( IntData,default)
    protected fun uInt(default:UInt  = UInt.MIN_VALUE)= Prop( UIntData,default)
    protected fun long(default:Long  = Long.MIN_VALUE) = Prop( LongData,default)
    protected fun uLong(default:ULong  = ULong.MIN_VALUE) = Prop( ULongData,default)
    protected fun float(default:Float  = Float.MIN_VALUE)= Prop( FloatData,default)
    protected fun double(default:Double  = Double.MIN_VALUE) = Prop( DoubleData,default)
    protected fun <T : StructData<T>> struct(type:T) = Prop( type,type)
    //protected fun <T : StructData> struct(constructor: (ByteArray, Int) -> T): PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> = StructProp(this, constructor)

    override val size get() = currOffset


    fun store(array: ByteArray, offset: Int = 0, bigEndian: Boolean = false) = store(this as K, array, offset,bigEndian)

    override fun storeLE(value: K, array: ByteArray, offset: Int){
        require(offset >= 0 && offset + size <= array.size){ "Struct does not fit in data" }

        var initSize = 0
        for (field in fields) {
            field.store( array, offset + initSize)
            initSize += field.size
        }
    }

    override fun parseLE(array: ByteArray, offset: Int) : K{
        require(offset >= 0 && offset + size <= array.size){ "Struct does not fit in data" }
        var initSize = 0
        for (field in fields) {
            field.parse(array, offset + initSize)
            initSize += field.size
        }
        return this as K
    }

    override fun toString(): String {
        val sb = StringBuilder()
        fields.forEachIndexed { index, prop ->
            sb.append("$index:${prop.propName}=${prop.inst}\n")
        }
        return sb.toString()
    }

    inner class Prop<T>(val type: BinaryData<T>,var inst: T) : ReadWriteProperty<Any,T>,BinaryData<T> by type {

        init {
            currOffset += type.size
            fields.add(this)
        }

        var propName:String?=null

        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            if(propName==null) propName = property.name
            return inst
        }

        override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
            inst = value
        }

        fun parse(array: ByteArray, offset: Int){
            inst = parseLE(array, offset)
        }

        fun store(array: ByteArray, offset: Int) {
            storeLE(inst, array, offset)
        }
    }
}




//internal class Prop<T>(private val owner: StructData, private val type: BinaryData<T>) : PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> {
//    override fun provideDelegate(thisRef: Any?, property: KProperty<*>): BoundProp<T> {
//        val offset = owner.currOffset
//        owner.currOffset += type.size
//        return BoundProp(owner.data, type, offset)
//    }
//}
//
//internal class StructProp<T : StructData>(private val owner: StructData, private val constructor: (ByteArray, Int) -> T) : PropertyDelegateProvider<Any?, ReadWriteProperty<Any?, T>> {
//    override fun provideDelegate(thisRef: Any?, property: KProperty<*>): BoundStructProp<T> {
//        val inst = constructor(owner.data, owner.currOffset)
//        owner.currOffset += inst.size
//        return BoundStructProp(owner.data, inst, 0)
//    }
//}
//
//internal class BoundProp<T>(private val data: ByteArray, private val inst: BinaryData<T>, private val offset: Int) : ReadWriteProperty<Any?, T> {
//    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
//        return inst.parseLE(data, offset)
//    }
//
//    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
//        inst.storeLE(value, data, offset)
//    }
//}
//
//
//internal class BoundStructProp<T : StructData>(private val data: ByteArray, private val inst: T, private val offset: Int) : ReadWriteProperty<Any?, T> {
//    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
//        return inst
//    }
//
//    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
//        throw UnsupportedOperationException()
//    }
//}
